package simplifiedPastry;

import java.math.BigInteger;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

import simplifiedPastry.Node.NodeEntry;

class LeafSet {
	
	public static class DistanceComparator implements Comparator<NodeEntry> {
		
		private NodeId mainNode;
		
		public DistanceComparator(NodeId mainNode) {
			this.mainNode = mainNode;
		}

		@Override
		public int compare(NodeEntry o1, NodeEntry o2) {
			BigInteger d1 = mainNode.distance(o1.nodeId);
			BigInteger d2 = mainNode.distance(o2.nodeId);
			return d1.compareTo(d2);
		}
		
	};
	
	private TreeSet<NodeEntry> leafSetSetSmaller, leafSetSetBigger, leafSetSet;
	private NodeId nodeId;
	private NodeEntry nodeEntry;
	
	public LeafSet(NodeEntry nodeEntry) {
		DistanceComparator distanceComparator = new DistanceComparator(nodeEntry.nodeId);
		leafSetSetSmaller	= new TreeSet<NodeEntry>(distanceComparator);
		leafSetSetBigger	= new TreeSet<NodeEntry>(distanceComparator);
		leafSetSet			= new TreeSet<NodeEntry>(distanceComparator);
		this.nodeEntry		= nodeEntry;
		this.nodeId			= nodeEntry.nodeId;
	}
	
	synchronized public void fillLeafSetByteData(byte[] buff, int offset) {
		if (leafSetSet.size() == 0) {
			return;
		}
		Iterator<NodeEntry> iter = leafSetSet.iterator();
		while (iter.hasNext()) {
			NodeEntry entry = iter.next();
			entry.fillByteBuff(buff, offset);
			offset += 22;
		}
	}
	
	synchronized public NodeEntry[] getKLeafSet(int num) {
		if (num > ProtocolVariables.leafSetSize) {
			return new NodeEntry[0];
		}
		int max = (num < leafSetSet.size()) ? num : leafSetSet.size();
		NodeEntry[] nodeEntryArray = new NodeEntry[max];
		Iterator<NodeEntry> iter = leafSetSet.iterator();
		int i = 0;
		while (iter.hasNext()) {
			nodeEntryArray[i] = iter.next();
			if (i++ >= max) {
				break;
			}
		}
		return nodeEntryArray;
	}
	
	synchronized public NodeEntry[] getLeafSet() {
		return getKLeafSet(ProtocolVariables.leafSetSize);
	}
	
	synchronized public int getSize() {
		return leafSetSet.size();
	}
	
	synchronized public void addNodeEntry(NodeEntry nodeEntry) {
		if (nodeEntry.nodeId.compareTo(nodeId) == 0) {
			return ;
		}
		leafSetSet.add(nodeEntry);
		if (leafSetSet.size() > ProtocolVariables.leafSetSize) {
			leafSetSet.pollLast();
		}
		if (nodeId.compareTo(nodeEntry.nodeId) < 0) {
			leafSetSetSmaller.add(nodeEntry);
			if (leafSetSetSmaller.size() > ProtocolVariables.leafSetSize/2) {
				leafSetSetSmaller.pollLast();
			}
		} else {
			leafSetSetBigger.add(nodeEntry);
			if (leafSetSetBigger.size() > ProtocolVariables.leafSetSize/2) {
				leafSetSetBigger.pollLast();
			}
		}
		
	}
	
	synchronized public void removeNodeEntry(NodeEntry nodeEntry) {
		leafSetSet.remove(nodeEntry);
		leafSetSetSmaller.remove(nodeEntry);
		leafSetSetBigger.remove(nodeEntry);
	}
	
	synchronized public NodeEntry getNextNodeEntry(NodeId searchId) {
		Iterator<NodeEntry> i = leafSetSet.iterator();
		NodeEntry closestEntry = nodeEntry;
		BigInteger closestDistance = nodeEntry.nodeId.distance(searchId);
		while (i.hasNext()) {
			NodeEntry entry = i.next();
			BigInteger distance = entry.nodeId.distance(searchId);
			if (distance.compareTo(closestDistance) < 0) {
				closestDistance = distance;
				closestEntry = entry;
			}
		}
		if (closestEntry == nodeEntry) {
			return null;
		}
		return closestEntry;
	}

	@Override
	public String toString() {
		Iterator<NodeEntry> iter = leafSetSet.iterator();
		StringBuilder s = new StringBuilder();
		s.append("[");
		while (iter.hasNext()) {
			NodeEntry e = iter.next();
			s.append(e.nodeId + " " + e.inetAddress + " " + e.port + "\n,");
		}
		s.insert(s.length()-1, ']');
		return s.toString();
	}
}
